﻿///////////////////////////////////////////////////////////////////////////////
//
// File
//  VirtualMachine.cs
//
///////////////////////////////////////////////////////////////////////////////


using System;
using System.IO;
using System.Management;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Security.AccessControl;
using System.Linq;
using System.Text;
using System.Threading;
using Ionic.Zip;
using Microsoft.Samples.ServiceHosting.StorageClient;


namespace CloudBackupLib
{
    ///////////////////////////////////////////////////////////////////////////
    //
    // Name
    //  VirtualMachine
    //
    // Remarks
    //  This class represents both the VirtualMachine in the Hyper-V server
    //  and it's stored version on the Azure server.  It's methods provide 
    //  the ability to upload, download, export, and import.
    //
    //  This class is not thread-safe and meant to be used by a single thread
    //  at a time.  The caller is responsible for any concurrent usage.    
    //
    ///////////////////////////////////////////////////////////////////////////
    public class VirtualMachine
    {
        public enum State
        {
            Invalid,
            Remote,
            Stopped,
            Running,
            Saved,
            Paused,
            Changing,
        };

        private enum Operation
        {
            None,
            Suspend,
            Resume,
            Export,
            Compress,
            Uncompress,
            Import,
            Download,
            Upload,
            Delete
        };

        public bool IsLocal { get; private set; } // True for HyperV, False for Azure VMs
        public string Name { get; private set; }
        public DateTime LastModified { get; private set; }

        private int _Id;
        private static int _IdCounter = 1;

        // Operation variables
        //
        private Operation _CurrentOperation = Operation.None; // Only one operation can be outstanding at a time
        private ManagementObject _Job; // The current WMI job if there is one in progress
        private Timer _JobTimer; // Used to poll the progress of outstanding jobs
        private Timer _CancelTimer; // Used when a cancel operation call has been initiated

        // Compression Progress
        //
        private decimal _TotalUncompressedSize; // During a compress operation, this is the size of all files that will be compressed
        private decimal _UncompressedSize; // During the compress operation, this is the amount of bytes in progress relative to _TotalUncompressedSize
        private long _LastBytesTransferred = 0;

        // Azure
        //
        static private BlobContainer _AzureContainer;

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  VirtualMachine Constructor
        //
        // Parameters
        //  name : The simple name of the VM.  
        //  isLocal : True if the the VM object is being created to represent
        //      a VM managed by the Hyper-V server, false if the VM object is
        //      being created to represent an Azure-stored VM
        // lastModified : [optional] The time at which the VM was last 
        //      modified(saved) for remote VM backups
        //
        ///////////////////////////////////////////////////////////////////////
        public VirtualMachine(string name, bool isLocal, DateTime lastModified)
        {
            Name = name;
            IsLocal = isLocal;
            LastModified = lastModified;
            _Id = _IdCounter++;
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  CurrentState
        // 
        // Returns
        //  The state of the virtual machine.
        //
        ///////////////////////////////////////////////////////////////////////
        public State CurrentState
        {
            get
            {
                if(IsLocal == false)
                {
                    return State.Remote;
                }
                else
                {
                    ManagementObject computerSystem = GetComputerSystem();
                    if(computerSystem == null)
                    {
                        return State.Invalid;
                    }

                    switch((UInt16)computerSystem["EnabledState"])
                    {
                        case 0:
                        {
                            return State.Invalid;
                        }
                        case 2:
                        {
                            return State.Running;
                        }
                        case 3:
                        {
                            return State.Stopped;
                        }
                        case 32768:
                        {
                            return State.Paused;
                        }
                        case 32769:
                        {
                            return State.Saved;
                        }
                        default:
                        {
                            return State.Changing;
                        }
                    }
                }
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  GetPercentComplete
        // 
        // Returns
        //  The current completion stage of any outstanding operation. 
        //  Will return null if there is no outstanding operation
        //
        ///////////////////////////////////////////////////////////////////////
        public int? GetPercentComplete()
        {
            // Take a local reference to the job in case it completes while
            // this method is being called and the member variable is nulled
            //
            ManagementObject job = _Job;

            if(job != null) // WMI operations will use the job object
            {
                job.Get();

                return (UInt16)job["PercentComplete"];
            }
            else if(_TotalUncompressedSize > 0) // compression operations will use these variables
            {
                return (int)((_UncompressedSize / _TotalUncompressedSize) * 100);
            }
            else if(GetAzureContainer().GetPercentComplete() != null)
            {
                return GetAzureContainer().GetPercentComplete();
            }
            else
            {
                return null;
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  Suspend
        //
        // Description
        //  Suspends a local VM.  Does nothing for remote VMs
        //
        // Parameters
        //  callback : [optional] A delegate callback that will receive the 
        //      results of the operation for asynchronous execution.  If null 
        //      is specified, the method is executed synchronously.  The 
        //      returned VirtualMachine refers to the invoked object.  The bool
        //      value indicates success/failure
        //
        // Returns
        //  True if the method succeeds, false otherwise.  If a callback 
        //  is not supplied, the return value is the result of the suspend
        //  operation.  If a callback is supplied, the result is returned
        //  through the callback
        //
        // Remarks
        //  This method may execute the callback immediately, so the caller
        //  will want to handle re-entrancy, such as through the dispatcher of 
        //  the process's message handler thread.
        //
        ///////////////////////////////////////////////////////////////////////
        public bool Suspend(Action<VirtualMachine, bool> callback)
        {
            // Do nothing if there is already an operation outstanding
            //
            if(_CurrentOperation != Operation.None)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;            
            }

            State state = CurrentState;

            // No need to suspend if already suspended
            //
            if(state == State.Saved)
            {
                if(callback != null)
                {
                    callback(this, true);
                }
                return true;
            }

            // Fail if the state is not ready to suspend
            //
            if(state != State.Running && state != State.Paused)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            // Initiate the suspend operation through the Hyper-V WMI API
            //
            if(IsLocal)
            {
                ManagementObject computerSystem = GetComputerSystem();
                ManagementBaseObject inParameters = computerSystem.GetMethodParameters("RequestStateChange");
                inParameters["RequestedState"] = 32769;

                // Start the operation
                //
                _CurrentOperation = Operation.Suspend;
                ManagementBaseObject outParameters = computerSystem.InvokeMethod("RequestStateChange", inParameters, null);

                // Retrieve the job object from the out paramters
                //
                _Job = new ManagementObject(@"root\virtualization", (string)outParameters["Job"], null);

                if(callback != null) // Async suspend
                {
                    // Set a timer to check on the completion status of the job
                    //
                    _JobTimer = new Timer(OnJobTimer, callback, 100, Timeout.Infinite);

                    return true;
                }
                else // Sync suspend
                {
                    bool result = WaitForCompletion(_Job);
                    
                    _Job = null;
                    _CurrentOperation = Operation.None;

                    return result;
                }
            }   
            else // Cannot suspend a remote machine
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  Resume
        //
        // Description
        //  Resumes a local VM.  Does nothing for remote VMs
        //
        // Parameters
        //  callback : [optional] A delegate callback that will receive the 
        //      results of the operation for asynchronous execution.  If null 
        //      is specified, the method is executed synchronously.  The 
        //      returned VirtualMachine refers to the invoked object.  The bool
        //      value indicates success/failure
        //
        // Returns
        //  True if the method succeeds, false otherwise.  If a callback 
        //  is not supplied, the return value is the result of the suspend
        //  operation.  If a callback is supplied, the result is returned
        //  through the callback
        //
        // Remarks
        //  This method may execute the callback immediately, so the caller
        //  will want to handle re-entrancy, such as through the dispatcher of 
        //  the process's message handler thread.
        //
        ///////////////////////////////////////////////////////////////////////
        public bool Resume(Action<VirtualMachine, bool> callback)
        {
            // Do nothing if there is already an operation outstanding
            //
            if(_CurrentOperation != Operation.None)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            State state = CurrentState;

            // No need to resume if already running
            //
            if(state == State.Running)
            {
                if(callback != null)
                {
                    callback(this, true);
                }
                return true;
            }

            // Fail if the state is not ready to resume, but allow resuming after an import for remote machines
            //
            if(state != State.Saved && state != State.Remote)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            // Initiate the resume operation through the Hyper-V WMI API
            //
            ManagementObject computerSystem = GetComputerSystem();
            ManagementBaseObject inParameters = computerSystem.GetMethodParameters("RequestStateChange");
            inParameters["RequestedState"] = 2;

            // Start the operation
            //
            _CurrentOperation = Operation.Resume;
            ManagementBaseObject outParameters = computerSystem.InvokeMethod("RequestStateChange", inParameters, null);

            // Retrieve the job object from the out paramters
            //
            _Job = new ManagementObject(@"root\virtualization", (string)outParameters["Job"], null);

            if(callback != null) // Async suspend
            {
                // Set a timer to check on the completion status of the job
                //
                _JobTimer = new Timer(OnJobTimer, callback, 100, Timeout.Infinite);

                return true;
            }
            else // Sync suspend
            {
                bool result = WaitForCompletion(_Job);

                _Job = null;
                _CurrentOperation = Operation.None;

                return result;
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  Delete
        //
        // Description
        //  Deletes the virtual machine.  For remote VMs, the azure storage
        //  is deleted.  For local VMs, the VM is deleted from the HyperV
        //  server.
        //
        // Parameters
        //  callback : [optional] A delegate callback that will receive the 
        //      results of the operation for asynchronous execution.  If null 
        //      is specified, the method is executed synchronously.  The 
        //      returned VirtualMachine refers to the invoked object.  The bool
        //      value indicates success/failure
        //
        // Returns
        //  True if the method succeeds, false otherwise.  If a callback 
        //  is not supplied, the return value is the result of the suspend
        //  operation.  If a callback is supplied, the result is returned
        //  through the callback
        //
        // Remarks
        //  This method may execute the callback immediately, so the caller
        //  will want to handle re-entrancy, such as through the dispatcher of 
        //  the process's message handler thread.
        //
        ///////////////////////////////////////////////////////////////////////
        public bool Delete(Action<VirtualMachine, bool> callback)
        {
            // Do nothing if there is already an operation outstanding
            //
            if(_CurrentOperation != Operation.None)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            if(IsLocal)
            {
                ManagementObject host = GetHost();
                ManagementBaseObject inParameters = host.GetMethodParameters("DestroyVirtualSystem");
                ManagementObject computerSystem = GetComputerSystem();

                inParameters["ComputerSystem"] = computerSystem.Path.Path;

                // Start the operation
                //
                _CurrentOperation = Operation.Suspend;
                ManagementBaseObject outParameters = host.InvokeMethod("DestroyVirtualSystem", inParameters, null);

                // Retrieve the job object from the out paramters
                //
                _Job = new ManagementObject(@"root\virtualization", (string)outParameters["Job"], null);

                if(callback != null) // Async suspend
                {
                    // Set a timer to check on the completion status of the job
                    //
                    _JobTimer = new Timer(OnJobTimer, callback, 100, Timeout.Infinite);

                    return true;
                }
                else // Sync suspend
                {
                    bool result = WaitForCompletion(_Job);

                    _Job = null;
                    _CurrentOperation = Operation.None;

                    return result;
                }
            }
            else
            {
                _CurrentOperation = Operation.Delete;

                Func<bool> action = delegate()
                {
                    string blobName = Name + ".zip";

                    bool result = true;

                    try
                    {
                        GetAzureContainer().DeleteBlob(blobName);
                    }
                    catch(Exception)
                    {
                        result = false;
                    }

                    if(callback != null)
                    {
                        callback(this, result);
                    }

                    _CurrentOperation = Operation.None;

                    return result;
                };

                if(callback != null)
                {
                    action.BeginInvoke(ar => action.EndInvoke(ar), null);
                    return true;
                }
                else
                {
                    return action();
                }
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  Export
        //
        // Description
        //  Exports a local VM.  Does nothing for remote VMs.
        //
        // Parameters
        //  destinationPath : The fully qualified path that will be used
        //      for the export operation.  A subdirection with the VM name
        //      will be created in this folder
        //  callback : [optional] A delegate callback that will receive the 
        //      results of the operation for asynchronous execution.  If null 
        //      is specified, the method is executed synchronously.  The 
        //      returned VirtualMachine refers to the invoked object.  The bool
        //      value indicates success/failure
        //
        // Returns
        //  True if the method succeeds, false otherwise.  If a callback 
        //  is not supplied, the return value is the result of the suspend
        //  operation.  If a callback is supplied, the result is returned
        //  through the callback
        //
        // Remarks
        //  This method may execute the callback immediately, so the caller
        //  will want to handle re-entrancy, such as through the dispatcher of 
        //  the process's message handler thread.
        //
        //  The method will attempt to delete any folder in the destination
        //  path with the name of the VM.        
        //
        ///////////////////////////////////////////////////////////////////////
        public bool Export(string destinationPath, Action<VirtualMachine, bool> callback)
        {
            // Do nothing if there is already an operation outstanding
            //
            if(_CurrentOperation != Operation.None)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            try
            {
                // Check for the existence of the destination path
                //
                if(Directory.Exists(destinationPath) == false)
                {
                    Directory.CreateDirectory(destinationPath);
                }

                // Make sure there isn't already an exported VM in the exportPath
                //
                string exportPath = Path.Combine(destinationPath, Name);
                if(Directory.Exists(exportPath))
                {
                    Directory.Delete(exportPath, true);
                }
            }
            catch(Exception)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            // Fail if the state is not ready to export
            //
            State state = CurrentState;
            if(state != State.Saved && state != State.Stopped)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            if(IsLocal)
            {
                ManagementObject host = GetHost();
                ManagementObject computerSystem = GetComputerSystem(); // A reference to the VM object

                // Assemble the method parameters
                //
                ManagementBaseObject inParameters = host.GetMethodParameters("ExportVirtualSystem");
                inParameters["ComputerSystem"] = computerSystem.Path.Path;
                inParameters["CopyVmState"] = true;
                inParameters["ExportDirectory"] = destinationPath;

                // Start the operation
                //
                _CurrentOperation = Operation.Export;
                ManagementBaseObject outParameters = host.InvokeMethod("ExportVirtualSystem", inParameters, null);

                LastModified = DateTime.Now;

                // Retrieve the job object from the out paramters
                //
                _Job = new ManagementObject(@"root\virtualization", (string)outParameters["Job"], null);

                if(callback != null) // Async export
                {
                    // Set a timer to check on the completion status of the job
                    //
                    _JobTimer = new Timer(OnJobTimer, callback, 100, Timeout.Infinite);

                    return true;
                }
                else // Sync export
                {
                    bool result = WaitForCompletion(_Job);

                    _Job = null;

                    _CurrentOperation = Operation.None;

                    return result;
                }
            }
            else // Cannot export a remote machine
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  Import
        //
        // Description
        //  Imports a downloaded VM.  
        //
        // Parameters
        //  sourcePath : The fully qualified path that will be used
        //      for the import operation.
        //  callback : [optional] A delegate callback that will receive the 
        //      results of the operation for asynchronous execution.  If null 
        //      is specified, the method is executed synchronously.  The 
        //      returned VirtualMachine refers to the invoked object.  The bool
        //      value indicates success/failure
        //
        // Returns
        //  True if the method succeeds, false otherwise.  If a callback 
        //  is not supplied, the return value is the result of the suspend
        //  operation.  If a callback is supplied, the result is returned
        //  through the callback
        //
        // Remarks
        //  This method may execute the callback immediately, so the caller
        //  will want to handle re-entrancy, such as through the dispatcher of 
        //  the process's message handler thread.
        //
        ///////////////////////////////////////////////////////////////////////
        public bool Import(string sourcePath, Action<VirtualMachine, bool> callback)
        {
            // Do nothing if there is already an operation outstanding
            //
            if(_CurrentOperation != Operation.None)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            // Make sure there isn't a VM with the same name already and the
            // source path exists
            //
            bool alreadyExists = GetLocalVirtualMachines().Any(vm => vm.Name == Name); 
            if(Directory.Exists(sourcePath) == false || alreadyExists)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            if(IsLocal == false)
            {
                ManagementObject host = GetHost();

                // Assemble the method parameters
                //
                ManagementBaseObject inParameters = host.GetMethodParameters("ImportVirtualSystem");
                inParameters["GenerateNewID"] = "false";
                inParameters["ImportDirectory"] = sourcePath;

                // Start the operation
                //
                ManagementBaseObject outParameters = host.InvokeMethod("ImportVirtualSystem", inParameters, null);

                // Retrieve the job object from the out paramters
                //
                _Job = new ManagementObject(@"root\virtualization", (string)outParameters["Job"], null);

                if(callback != null) // Async export
                {
                    // Set a timer to check on the completion status of the job
                    //
                    _JobTimer = new Timer(OnJobTimer, callback, 100, Timeout.Infinite);

                    return true;
                }
                else // Sync export
                {
                    bool result = WaitForCompletion(_Job);
                    _Job = null;

                    return result;
                }
            }
            else // Cannot import a local machine
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  Compress
        //
        // Description
        //  Compresses the exported VM.  Does nothing for remote
        //  VMs.  The local VM must have been exported before this method will
        //  work
        //
        // Parameters
        //  sourcePath : The fully qualified path that contains the exported
        //      virtual machine.
        //  destinationPath : The fully qualified path that will be used
        //      as the destination of the compressed VM
        //  callback : [optional] A delegate callback that will receive the 
        //      results of the operation for asynchronous execution.  If null 
        //      is specified, the method is executed synchronously.  The 
        //      returned VirtualMachine refers to the invoked object.  The bool
        //      value indicates success/failure
        //
        // Returns
        //  True if the method succeeds, false otherwise.  If a callback 
        //  is not supplied, the return value is the result of the suspend
        //  operation.  If a callback is supplied, the result is returned
        //  through the callback
        //
        // Remarks
        //  This method may execute the callback immediately, so the caller
        //  will want to handle re-entrancy, such as through the dispatcher of 
        //  the process's message handler thread.
        //
        ///////////////////////////////////////////////////////////////////////
        public bool Compress(string sourcePath, string destinationPath, Action<VirtualMachine, bool> callback)
        {
            // Do nothing if there is already an operation outstanding
            //
            if(_CurrentOperation != Operation.None)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            // Make sure there is a valid source location
            //
            if(Directory.Exists(sourcePath) == false)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            // Validate the destination path
            //
            if(Directory.Exists(destinationPath) == false)
            {
                Directory.CreateDirectory(destinationPath);
            }

            if(IsLocal)
            {
                // Calculate the total uncompressed size of all the
                // files in the export location
                //
                _TotalUncompressedSize = Directory.GetFiles(sourcePath, "*", SearchOption.AllDirectories).Sum(fileName => new FileInfo(fileName).Length);
                _UncompressedSize = 0;

                string destination = Path.Combine(destinationPath, Name) + ".zip";

                _CurrentOperation = Operation.Compress;

                // Create an action to wrap the compression code so it can
                // be executed concurrently on a ThreadPool thread
                //
                Func<bool> action = delegate()
                {
                    bool result = true;

                    try
                    {
                        using(ZipFile zip = new ZipFile())
                        {
                            zip.SaveProgress += OnCompressProgress;
                            zip.UseZip64WhenSaving = Zip64Option.Always;
                            zip.TempFileFolder = destinationPath;
                            zip.AddDirectory(sourcePath);

                            // Begin the compress operatation
                            //
                            zip.Save(destination);
                        }
                    }
                    catch(Exception)
                    {
                        result = false;
                    }

                    if(callback != null)
                    {
                        callback(this, result);
                    }

                    _CurrentOperation = Operation.None;
                    _TotalUncompressedSize = 0;
                    _UncompressedSize = 0;

                    return result;
                };

                if(callback != null)
                {
                    action.BeginInvoke(ar => action.EndInvoke(ar), null);
                    return true;
                }
                else
                {
                    return action();
                }
            }
            else // Do nothing for remote VMs
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  OnCompressProgress
        //
        // Description
        //  Callback delegate called during a compress operation
        //
        // Parameters
        //  sender : The object that is calling the delegate
        //  e : Contains the event args
        //
        ///////////////////////////////////////////////////////////////////////
        private void OnCompressProgress(object sender, SaveProgressEventArgs e)
        {
            if(_CancelTimer != null)
            {
                e.Cancel = true;
                return;
            }

            switch(e.EventType)
            {
                case ZipProgressEventType.Saving_EntryBytesRead:
                {
                    // Figure out how many bytes were transferred since the 
                    // last bytes read
                    //
                    long bytesTransferedSinceLast = e.BytesTransferred - _LastBytesTransferred;

                    _UncompressedSize += bytesTransferedSinceLast;

                    if(e.BytesTransferred == e.TotalBytesToTransfer)
                    {
                        _LastBytesTransferred = 0;
                    }
                    else
                    {
                        _LastBytesTransferred = e.BytesTransferred;
                    }

                    break;
                }
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  Uncompress
        //
        // Description
        //  Uncompresses a downloaded VM.  Does nothing for local
        //  VMs.  The remote VM must have been downloaded before this method will
        //  work
        //
        // Parameters
        //  sourcePath : The fully qualified path to the source compressed
        //      virtual machine
        //  destinationPath : The fully qualified path that will be used
        //      as the destination of the uncompressed VM
        //  callback : [optional] A delegate callback that will receive the 
        //      results of the operation for asynchronous execution.  If null 
        //      is specified, the method is executed synchronously.  The 
        //      returned VirtualMachine refers to the invoked object.  The bool
        //      value indicates success/failure
        //
        // Returns
        //  True if the method succeeds, false otherwise.  If a callback 
        //  is not supplied, the return value is the result of the suspend
        //  operation.  If a callback is supplied, the result is returned
        //  through the callback
        //
        // Remarks
        //  This method may execute the callback immediately, so the caller
        //  will want to handle re-entrancy, such as through the dispatcher of 
        //  the process's message handler thread.
        //
        ///////////////////////////////////////////////////////////////////////
        public bool Uncompress(string sourcePath, string destinationPath, Action<VirtualMachine, bool> callback)
        {
            // Do nothing if there is already an operation outstanding
            //
            if(_CurrentOperation != Operation.None)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            // Make sure there is a valid source location
            //
            if(File.Exists(sourcePath) == false)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            // Validate the destination path
            //
            if(Directory.Exists(destinationPath) == false)
            {
                Directory.CreateDirectory(destinationPath);
            }

            if(IsLocal == false)
            {
                // Calculate the total uncompressed size of all the
                // files in the export location
                //
                using(ZipFile zip = ZipFile.Read(sourcePath))
                {
                    _TotalUncompressedSize = zip.Entries.Sum(entry => entry.UncompressedSize);
                    _UncompressedSize = 0;
                }

                string destination = Path.Combine(destinationPath, Name);

                _CurrentOperation = Operation.Uncompress;

                // Create an action to wrap the uncompression code so it can
                // be executed concurrently on a ThreadPool thread
                //
                Func<bool> action = delegate()
                {
                    bool result = true;

                    try
                    {
                        using(ZipFile zip = ZipFile.Read(sourcePath))
                        {
                            zip.ExtractProgress += OnUncompressProgress;

                            // Begin the uncompress operatation
                            //
                            zip.ExtractAll(destination, true);
                        }
                    }
                    catch(Exception)
                    {
                        result = false;
                    }

                    if(callback != null)
                    {
                        callback(this, result);
                    }

                    _CurrentOperation = Operation.None;
                    _TotalUncompressedSize = 0;
                    _UncompressedSize = 0;

                    return result;
                };

                if(callback != null)
                {
                    action.BeginInvoke(ar => action.EndInvoke(ar), null);
                    return true;
                }
                else
                {
                    return action();
                }
            }
            else // Do nothing for local VMs
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  OnUncompressProgress
        //
        // Description
        //  Callback delegate called during a uncompress operation
        //
        // Parameters
        //  sender : The object that is calling the delegate
        //  e : Contains the event args
        //
        ///////////////////////////////////////////////////////////////////////
        private void OnUncompressProgress(object sender, ExtractProgressEventArgs e)
        {
            if(_CancelTimer != null)
            {
                e.Cancel = true;
                return;
            }

            switch(e.EventType)
            {
                case ZipProgressEventType.Extracting_AfterExtractAll:
                {
                    _LastBytesTransferred = 0;
                    break;
                }
                case ZipProgressEventType.Extracting_EntryBytesWritten:
                {
                    // Figure out how many bytes were transferred since the 
                    // last bytes read
                    //
                    long bytesTransferedSinceLast = e.BytesTransferred - _LastBytesTransferred;

                    _UncompressedSize += bytesTransferedSinceLast;

                    if(e.BytesTransferred == e.TotalBytesToTransfer)
                    {
                        _LastBytesTransferred = 0;
                    }
                    else
                    {
                        _LastBytesTransferred = e.BytesTransferred;
                    }

                    break;
                }
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  Upload
        //
        // Description
        //  Begins an upload operation for local VMs.  Does nothing for remote
        //  VMs.  The local VM must have been exported before this method will
        //  work
        //
        // Parameters
        //  sourceFile : The fully qualified path that will be used
        //      for the upload operation.  It should correspond to the 
        //      compressed location of the VM
        //  callback : [optional] A delegate callback that will receive the 
        //      results of the operation for asynchronous execution.  If null 
        //      is specified, the method is executed synchronously.  The 
        //      returned VirtualMachine refers to the invoked object.  The bool
        //      value indicates success/failure
        //
        // Returns
        //  True if the method succeeds, false otherwise.  If a callback 
        //  is not supplied, the return value is the result of the suspend
        //  operation.  If a callback is supplied, the result is returned
        //  through the callback
        //
        // Remarks
        //  This method may execute the callback immediately, so the caller
        //  will want to handle re-entrancy, such as through the dispatcher of 
        //  the process's message handler thread.
        //
        ///////////////////////////////////////////////////////////////////////
        public bool Upload(string sourceFile, Action<VirtualMachine, bool> callback)
        {
            // No nothing if there is already an operation outstanding
            //
            if(_CurrentOperation != Operation.None)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            // Verify the source file exists
            //
            if(File.Exists(sourceFile) == false)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            if(IsLocal)
            {
                _CurrentOperation = Operation.Upload;

                Func<bool> action = delegate()
                {
                    bool result = true;

                    try
                    {
                        // Upload the compressed virtual machine
                        //
                        using(FileStream fileStream = new FileStream(sourceFile, FileMode.Open, FileSystemRights.ReadData, FileShare.Read, 4 * 1024 * 1024, FileOptions.SequentialScan | FileOptions.Asynchronous))
                        {
                            BlobProperties blobProperties = new BlobProperties(Path.GetFileName(sourceFile));
                            NameValueCollection metaData = new NameValueCollection();
                            metaData["VirtualMachineName"] = Name;
                            metaData["LastModified"] = LastModified.ToFileTime().ToString();
                            blobProperties.Metadata = metaData;
                            
                            result = GetAzureContainer().CreateBlob(blobProperties, new BlobContents(fileStream), true);
                        }
                    }
                    catch(Exception)
                    {
                        result = false;
                    }

                    if(callback != null)
                    {
                        callback(this, result);
                    }

                    _CurrentOperation = Operation.None;

                    return result;
                };

                if(callback != null)
                {
                    action.BeginInvoke(ar => action.EndInvoke(ar), null);
                    return true;
                }
                else
                {
                    return action();
                }
            }
            else // Do nothing for remote virtual machines
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  Download
        //
        // Description
        //  Begins a download operation for remote VMs.  Does nothing for local
        //  VMs.
        //
        // Parameters
        //  destinationPath : The fully qualified path that will be used
        //      for the download operation.
        //  callback : [optional] A delegate callback that will receive the 
        //      results of the operation for asynchronous execution.  If null 
        //      is specified, the method is executed synchronously.  The 
        //      returned VirtualMachine refers to the invoked object.  The bool
        //      value indicates success/failure
        //
        // Returns
        //  True if the method succeeds, false otherwise.  If a callback 
        //  is not supplied, the return value is the result of the suspend
        //  operation.  If a callback is supplied, the result is returned
        //  through the callback
        //
        // Remarks
        //  This method may execute the callback immediately, so the caller
        //  will want to handle re-entrancy, such as through the dispatcher of 
        //  the process's message handler thread.
        //
        ///////////////////////////////////////////////////////////////////////
        public bool Download(string destinationPath, Action<VirtualMachine, bool> callback)
        {
            // No nothing if there is already an operation outstanding
            //
            if(_CurrentOperation != Operation.None)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            // Verify the destination path exists
            //
            if(Directory.Exists(destinationPath) == false)
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }

            if(IsLocal == false)
            {
                _CurrentOperation = Operation.Download;

                Func<bool> action = delegate()
                {
                    bool result = true;

                    try
                    {
                        // Download the compressed virtual machine
                        //
                        string destination = Path.Combine(destinationPath, Name) + ".zip";
                        using(FileStream fileStream = new FileStream(destination, FileMode.Create, FileSystemRights.CreateFiles, FileShare.None, 4 * 1024 * 1024, FileOptions.SequentialScan | FileOptions.Asynchronous | FileOptions.WriteThrough))
                        {
                            GetAzureContainer().GetBlob(Name + ".zip", new BlobContents(fileStream), true);
                        }
                    }
                    catch(Exception)
                    {
                        result = false;
                    }

                    if(callback != null)
                    {
                        callback(this, result);
                    }

                    _CurrentOperation = Operation.None;

                    return result;
                };

                if(callback != null)
                {
                    action.BeginInvoke(ar => action.EndInvoke(ar), null);
                    return true;
                }
                else
                {
                    return action();
                }
            }
            else // Do nothing for local virtual machines
            {
                if(callback != null)
                {
                    callback(this, false);
                }
                return false;
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  Cancel
        //
        // Description
        //  Cancels any outstanding operation for the virtual machine
        //
        // Parameters
        //  callback : [optional] A delegate callback that will receive the 
        //      results of the operation for asynchronous execution.  If null 
        //      is specified, the method is executed synchronously.  The 
        //      returned VirtualMachine refers to the invoked object.  The bool
        //      value indicates success/failure
        //
        // Remarks
        //  This method may execute the callback immediately, so the caller
        //  will want to handle re-entrancy, such as through the dispatcher of 
        //  the process's message handler thread.
        //
        //  The callback will always be executed, and will always be after
        //  any operation callback previously specified
        //
        ///////////////////////////////////////////////////////////////////////
        public void Cancel(Action<VirtualMachine, bool> callback)
        {
            if(_CancelTimer == null)
            {
                GetAzureContainer().CancelOperation();
                _CancelTimer = new Timer(OnCancelTimer, callback, 1000, Timeout.Infinite);
            }

            // Spin while the operation completes
            //
            if(callback == null)
            {
                while(_CurrentOperation != Operation.None)
                {
                    Thread.Sleep(500);
                }
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  OnCancelTimer
        //
        // Parameters
        //  The callback that was previously passed to the Cancel method
        //
        // Returns
        //  A list of virtual machines that are contained by the local
        //  Hyper-V server
        //
        ///////////////////////////////////////////////////////////////////////
        private void OnCancelTimer(Object context)
        {
            if(_CurrentOperation == Operation.None)
            {
                Action<VirtualMachine, bool> callback = (Action<VirtualMachine, bool>)context;

                if(callback != null)
                {
                    callback(this, true);
                }

                _CancelTimer = null;
            }
            else
            {
                _CancelTimer.Change(200, Timeout.Infinite);
            }
        }



        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  GetLocalVirtualMachines
        //
        // Returns
        //  A list of virtual machines that are contained by the local
        //  Hyper-V server
        //
        ///////////////////////////////////////////////////////////////////////
        public static List<VirtualMachine> GetLocalVirtualMachines()
        {
            List<VirtualMachine> virtualMachines = new List<VirtualMachine>();

            // Enumerate all the computer systems in the virtualization system.
            // This includes the physical host computer as well
            //
            ManagementObjectSearcher searcher = new ManagementObjectSearcher(@"root\virtualization", @"select * from Msvm_ComputerSystem");
            foreach(ManagementObject computerSystem in searcher.Get())
            {
                // Retrieve the virtual machine settings for the computer system
                //
                ManagementObject settings = computerSystem.GetRelated("Msvm_VirtualSystemSettingData",
                    "Msvm_SettingsDefineState", null, null, "SettingData",
                    "ManagedElement", false, null).Cast<ManagementObject>().FirstOrDefault();

                // The physical host computer will not have virtual machine settings
                //
                if(settings != null)
                {
                    string name = (string)settings["ElementName"]; // The name of the virtual machine

                    // Create the object representation of the virtual machine
                    //
                    VirtualMachine virtualMachine = new VirtualMachine(name, true, DateTime.Now);

                    virtualMachines.Add(virtualMachine);
                }
            }

            return virtualMachines.OrderBy(vm => vm.Name).ToList();
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  GetRemoteVirtualMachines
        //
        // Returns
        //  A list of remote virtual machines
        //
        ///////////////////////////////////////////////////////////////////////
        public static List<VirtualMachine> GetRemoteVirtualMachines()
        {
            List<VirtualMachine> virtualMachines = new List<VirtualMachine>();

            try
            {
                foreach(BlobProperties blobProperties in GetAzureContainer().ListBlobs("", false))
                {
                    BlobProperties fullBlobProperties = GetAzureContainer().GetBlobProperties(blobProperties.Name);

                    string name = fullBlobProperties.Metadata["VirtualMachineName"];
                    DateTime lastModified = DateTime.FromFileTime(long.Parse(fullBlobProperties.Metadata["LastModified"]));
                    VirtualMachine virtualMachine = new VirtualMachine(name, false, lastModified);

                    virtualMachines.Add(virtualMachine);
                }
            }
            catch(Exception)
            {

            }

            return virtualMachines.OrderBy(vm => vm.Name).ToList();
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  GetComputerSystem
        //
        // Returns
        //  The ManagementObject that represents the computer system for
        //  the virtual machine name.
        //
        ///////////////////////////////////////////////////////////////////////
        private ManagementObject GetComputerSystem()
        {
            ManagementObject computerSystem = new ManagementObjectSearcher(@"root\virtualization", string.Format(@"select * from Msvm_ComputerSystem Where ElementName='{0}'", Name)).Get().Cast<ManagementObject>().FirstOrDefault();

            return computerSystem;
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  GetHost
        //
        // Returns
        //  The ManagementObject that represents the virtual system
        //  management service
        //
        ///////////////////////////////////////////////////////////////////////
        private ManagementObject GetHost()
        {
            ManagementObject host = new ManagementClass(@"root\virtualization", @"Msvm_VirtualSystemManagementService", null).GetInstances().Cast<ManagementObject>().FirstOrDefault();

            return host;
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  OnJobTimer
        //
        // Description
        //  Called by a ThreadPool thread periodically during a job operation
        //
        // Parameters
        //  context : A context object supplied to the timer
        //
        ///////////////////////////////////////////////////////////////////////
        private void OnJobTimer(Object context)
        {
            // Retrieve the latest job status information
            //
            _Job.Get();            

            // Check to see if the job is still running
            //
            bool jobStillRunning = (UInt16)_Job["JobState"] == 3 || (UInt16)_Job["JobState"] == 4;
            if(jobStillRunning && _CancelTimer == null)
            {
                // Reschedule the timer
                //
                _JobTimer.Change(100, Timeout.Infinite);
            }
            else // The job completed
            {
                Action<VirtualMachine, bool> callback = context as Action<VirtualMachine, bool>;

                bool jobSucceeded = (UInt16)_Job["JobState"] == 7;

                // Notify the caller of completion and result
                //
                if(callback != null)
                {
                    callback(this, jobSucceeded);
                }

                // If the job was cancelled by the user, delete the job so it terminates
                //
                if(_CancelTimer != null)
                {
                    // Attempt to terminate the job gracefully
                    //
                    ManagementBaseObject inParameters = _Job.GetMethodParameters("RequestStateChange");
                    inParameters["RequestedState"] = "4";

                    _Job.InvokeMethod("RequestStateChange", inParameters, null);
                }

                // Reset operation variables
                //
                _Job = null;
                _JobTimer = null;

                _CurrentOperation = Operation.None;
            }
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  GetAzureContainer
        //
        // Returns
        //  The top level container in the Azure blob storage for VMs
        //
        ///////////////////////////////////////////////////////////////////////
        private static BlobContainer GetAzureContainer()
        {
            if(_AzureContainer == null)
            {
                // Get access to the Azure blob storage object
                //
                StorageAccountInfo account = StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration();
                BlobStorage blobStorage = BlobStorage.Create(account);
                blobStorage.RetryPolicy = RetryPolicies.RetryN(2, TimeSpan.FromMilliseconds(100));

                // Get the top level container, creating it if it doesn't exist
                //
                string containerName = StorageAccountInfo.GetConfigurationSetting("ContainerName", null, true);
                _AzureContainer = blobStorage.GetBlobContainer(containerName);
                NameValueCollection containerMetadata = new NameValueCollection();
                containerMetadata.Add("Name", "CloudBackup");
                _AzureContainer.CreateContainer(containerMetadata, ContainerAccessControl.Private);
            }

            return _AzureContainer;
        }

        ///////////////////////////////////////////////////////////////////////
        //
        // Name
        //  WaitForCompletion
        //
        // Description
        //  Spins until the argument job has completed.
        //
        // Parameters
        //  job : The job to wait for
        //
        // Returns
        //  True if the job completed succesfully, false otherise
        //
        ///////////////////////////////////////////////////////////////////////
        private bool WaitForCompletion(ManagementObject job)
        {
            // Wait until the job completes
            //
            while(true)
            {
                // Retrieve the latest job status information
                //
                job.Get();

                // Check to see if the job is still running
                //
                if((UInt16)job["JobState"] == 3 || (UInt16)job["JobState"] == 4)
                {
                    Thread.Sleep(100);
                }
                else
                {
                    break;
                }
            }

            // Check to see if the job completed successfully
            //
            if((UInt16)job["JobState"] == 7)
            {
                return true;
            }
            else
            {
                return false;
            }
        }


    }
}
